using System;
using Directive = Velocity4Net.Runtime.Directives.Directive;
using Parse = Velocity4Net.Runtime.Directives.Parse;
using Parser = Velocity4Net.Runtime.Parse.Parser;
using MethodInvocationException = Velocity4Net.Errors.MethodInvocationException;
using ParseErrorException = Velocity4Net.Errors.ParseErrorException;
using ResourceNotFoundException = Velocity4Net.Errors.ResourceNotFoundException;
using InternalContextAdapter = Velocity4Net.Ctx.InternalContextAdapter;
using Velocity4Net.Errors;
using Velocity4Net.Runtime.Directives;
using System.IO;
using System.Text;

namespace Velocity4Net.Runtime.Parse.AST
{

    /// <summary>
    /// This class is responsible for handling the pluggable
    /// directives in VTL. ex.  #foreach()
    ///
    /// Please look at the Parser.jjt file which is
    /// what controls the generation of this class.
    /// </summary>
    /// <author> <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a></author>
    /// <author> <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a></author>
    /// <author> <a href="mailto:kav@kav.dk">Kasper Nielsen</a></author>
    /// <version> $Id: ASTDirective.cs,v 1.3 2003/10/27 13:54:10 corts Exp $</version>
    public class ASTDirective : SimpleNode
    {
        private Directive directive = null;
        private String directiveName = "";
        private bool isDirective;
        private bool isInitialized;

        public ASTDirective(int id) : base(id) { }

        public ASTDirective(Parser p, int id) : base(p, id) { }


        /**
         * @see org.apache.velocity.runtime.parser.node.SimpleNode#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object)
         */
        public override Object Init(InternalContextAdapter context, Object data)
        {
            /** method is synchronized to avoid concurrent directive initialization **/
            lock (this)
            {
                if (!isInitialized)
                {
                    base.Init(context, data);

                    /*
                     *  only do things that are not context dependent
                     */

                    if (parser.isDirective(directiveName))
                    {
                        isDirective = true;

                        try
                        {
                            directive = (Directive)Activator.CreateInstance(parser.getDirective(directiveName)
                                .GetType(), new object[] { });
                        }
                        catch (TypeInitializationException e)
                        {
                            throw new ApplicationException("Couldn't initialize " +
                                    "directive of class " +
                                    parser.getDirective(directiveName).GetType().Name,
                                    e);
                        }
                        catch (TypeLoadException e)
                        {
                            throw new ApplicationException("Couldn't initialize " +
                                    "directive of class " +
                                    parser.getDirective(directiveName).GetType().Name,
                                    e);
                        }

                        directive.setLocation(Line, Column, TemplateName);
                        directive.Init(rsvc, context, this);
                    }
                    else if (directiveName.StartsWith("@"))
                    {
                        if (this.jjtGetNumChildren() > 0)
                        {
                            // block macro call (normal macro call but has AST body)
                            directiveName = directiveName.Substring(1);

                            directive = new BlockMacro(directiveName);
                            directive.setLocation(Line, Column, TemplateName);

                            try
                            {
                                directive.Init(rsvc, context, this);
                            }
                            catch (TemplateInitException die)
                            {
                                throw new TemplateInitException(die.Message,
                                    (ParseException)die.WrappedThrowable,
                                    die.TemplateName,
                                    die.ColumnNumber + Column,
                                    die.LineNumber + Line);
                            }
                            isDirective = true;
                        }
                        else
                        {
                            // this is a fake block macro call without a body. e.g. #@foo
                            // just render as it is
                            isDirective = false;
                        }
                    }
                    else
                    {
                        /**
                         * Create a new RuntimeMacro
                         */
                        directive = new RuntimeMacro(directiveName);
                        directive.setLocation(Line, Column, TemplateName);

                        /**
                         * Initialize it
                         */
                        try
                        {
                            directive.Init(rsvc, context, this);
                        }

                        /**
                         * correct the line/column number if an exception is caught
                         */
                        catch (TemplateInitException die)
                        {
                            throw new TemplateInitException(die.Message,
                                    (ParseException)die.WrappedThrowable,
                                    die.TemplateName,
                                    die.ColumnNumber + Column,
                                    die.LineNumber + Line);
                        }
                        isDirective = true;
                    }

                    isInitialized = true;
                }

                return data;
            }
        }

        /**
         * @see org.apache.velocity.runtime.parser.node.SimpleNode#render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer)
         */
        public override bool Render(InternalContextAdapter context, TextWriter writer)
        {
            /*
             *  normal processing
             */

            if (isDirective)
            {
                directive.Render(context, writer, this);
            }
            else
            {
                writer.Write("#");
                writer.Write(directiveName);
            }

            return true;
        }

        /**
         *   Sets the directive name.  Used by the parser.  This keeps us from having to
         *   dig it out of the token stream and gives the parse the change to override.
         * @param str
         */
        public void setDirectiveName(String str)
        {
            directiveName = str;
        }

        /**
         *  Gets the name of this directive.
         *  @return The name of this directive.
         */
        public String DirectiveName
        {
            get
            {
                return directiveName;
            }
            set
            {
                directiveName = value;
            }
        }

        /**
         * @since 1.5
         */
        public override String ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(base.ToString());
            sb.Append(DirectiveName);
            return sb.ToString();
        }

    }
}
